//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.UIDesigner
{
    #region Using directives
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;

    using System.Workflow.ComponentModel;

    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using System.Resources;
    #endregion

    public class ParameterConnectionElement : Canvas, IUpdatePointPositions
    {
        private FrameworkElement inputParamElem;
        private FrameworkElement outputParamElem;
        private DataTypeModel dataModel;
        private Polyline line;
        private bool isCollapsedActivityLine;

        /// <summary>
        /// The composer control holding this element.
        /// </summary>
        private ComposerControl composerCtrl;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="inputParamElem">Destination of binding.</param>
        /// <param name="outputParamElem">Source of binding.</param>
        /// <param name="dataModel">Datatype model.</param>
        internal ParameterConnectionElement(
            FrameworkElement inputParamElem,
            FrameworkElement outputParamElem,
            DataTypeModel dataModel,
            bool isCollapsedActivityLine,
            ParameterDescriptionModel paramModel)
        {
            this.inputParamElem = inputParamElem;
            this.outputParamElem = outputParamElem;
            this.dataModel = dataModel;
            this.isCollapsedActivityLine = isCollapsedActivityLine;
            this.DataContext = paramModel;

            this.Loaded += new RoutedEventHandler(ParameterConnectionElement_Loaded);
            this.Unloaded += new RoutedEventHandler(ParameterConnectionElement_Unloaded);
            InitComponent();
        }
        
        /// <summary>
        /// The loaded handler for the control.
        /// </summary>
        /// <param name="sender">source</param>
        /// <param name="e">event handler</param>
        private void ParameterConnectionElement_Loaded(object sender, RoutedEventArgs e)
        {
            this.GetComposerControl();
            this.AddInteractionHandlers();
            this.UpdatePointPositions();
        }

        /// <summary>
        /// The Unloaded handler for the control.
        /// </summary>
        /// <param name="sender">source</param>
        /// <param name="e">event handler</param>
        private void ParameterConnectionElement_Unloaded(object sender, RoutedEventArgs e)
        {
            this.RemoveInteractionHandlers();
        }
        
        /// <summary>
        /// Add the interaction handlers according to the mode.
        /// </summary>
        private void AddInteractionHandlers()
        {
            if (this.composerCtrl != null && this.composerCtrl.CurrentRenderMode == RenderMode.ComposerMode)
            {
                // Make the ParameterConnectionElement to be focusable.
                // this is used when we select a Binding and hit delete button.
                this.Focusable = true;

                this.GotFocus += new RoutedEventHandler(this.OnGotFocus);

                // Create the contextMenu for the ParameterConnection.
                this.CreateContextMenu();

                // This is used to select the Parameter.
                this.PreviewMouseDown += new MouseButtonEventHandler(this.ParameterConnectionElement_PreviewMouseLeftButtonDown);

                this.RequestBringIntoView += new RequestBringIntoViewEventHandler(this.OnRequestBringIntoView);

                // This is used to delete the selected parameters.
                this.KeyDown += new KeyEventHandler(this.ParameterConnectionElement_PreviewKeyDown);
            }
        }

        /// <summary>
        /// Remove the interaction handlers according to the mode.
        /// </summary>
        private void RemoveInteractionHandlers()
        {
            if (this.composerCtrl != null && this.composerCtrl.CurrentRenderMode == RenderMode.ComposerMode)
            {
                this.GotFocus -= new RoutedEventHandler(this.OnGotFocus);

                // remove the contextMenu for the ParameterConnection.
                this.ContextMenu = null;

                this.PreviewMouseDown -= new MouseButtonEventHandler(this.ParameterConnectionElement_PreviewMouseLeftButtonDown);

                this.RequestBringIntoView -= new RequestBringIntoViewEventHandler(this.OnRequestBringIntoView);

                this.KeyDown -= new KeyEventHandler(this.ParameterConnectionElement_PreviewKeyDown);
            }
        }

        /// <summary>
        /// The destination of the binding.
        /// </summary>
        public FrameworkElement InputParameterElement
        {
            get { return inputParamElem; }
        }

        /// <summary>
        /// The source of the binding.
        /// </summary>
        public FrameworkElement OutputParameterElement
        {
            get { return outputParamElem; }
        }

        /// <summary>
        /// The datatype model of the binding.
        /// </summary>
        public DataTypeModel ParameterDataTypeModel
        {
            get { return this.dataModel; }
        }

        public bool IsCollapsedActivityLine
        {
            get
            {
                return this.isCollapsedActivityLine;
            }
        }

        /// <summary>
        /// This is a static function which is used to change the stlye of the 
        /// ParameterConnectionElement to selected style. 
        /// </summary>
        /// <param name="selectedElement">ParameterConnectionElement for which style has to be applied.</param>
        public static void ApplySelectedStyle(ParameterConnectionElement selectedElement)
        {
            selectedElement.line.StrokeThickness = 5;

            selectedElement.Opacity = 0.7;

            foreach (MenuItem item in selectedElement.ContextMenu.Items)
            {
                item.IsEnabled = true;
            }
        }

        /// <summary>
        /// This is a static function which is used to change the stlye of the 
        /// ParameterConnectionElement to DeSelected style. 
        /// </summary>
        /// <param name="deSelectedElement">ParameterConnectionElement for which style has to be applied.</param>
        public static void ApplyDeselectedStyle(ParameterConnectionElement deselectedElement)
        {
            deselectedElement.line.StrokeThickness = 2;
            deselectedElement.Opacity = 0.2;

            if (deselectedElement.ContextMenu != null)
            {
                foreach (MenuItem item in deselectedElement.ContextMenu.Items)
                {
                    item.IsEnabled = false;
                }
            }
        }

        /// <summary>
        /// Initialization.
        /// </summary>
        private void InitComponent()
        {
            this.line = new Polyline();            
            if (this.isCollapsedActivityLine)
            {
                BrushConverter converter = new BrushConverter();
                this.line.Stroke = converter.ConvertFrom(UIDesignerCodeConstants.LINECOLOR) as Brush;
            }
            else if (this.dataModel != null)
            {
                this.line.Stroke = new SolidColorBrush(this.dataModel.PrimaryEditorColor);
            }
            else
            {
                this.line.Stroke = new SolidColorBrush(Colors.Purple);
            }

            this.line.StrokeThickness = 2;
            this.Children.Add(line);
        }

        private void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
        {
            e.Handled = true;
        }

        private void OnGotFocus(object sender, RoutedEventArgs e)
        {
            e.Handled = true;
        }

        /// <summary>
        /// Refresh the point positions.
        /// </summary>
        public void UpdatePointPositions()
        {
            IBindingEndpoint startElement = inputParamElem as IBindingEndpoint;
            IBindingEndpoint endElement = outputParamElem as IBindingEndpoint;

            if (startElement != null && endElement != null)
            {
                Point startPoint = startElement.GetBindingStartPoint(this);
                Point endPoint = endElement.GetBindingEndpoint(this);

                if ((startPoint.X == 0 && startPoint.Y == 0) ||
                    (endPoint.X == 0 && endPoint.Y == 0))
                {
                    this.line.Visibility = Visibility.Hidden;
                }
                else
                {
                    double inputOffX = startPoint.X + 8.0;
                    double outputOffX = endPoint.X - 8.0;

                    Point inputOffPoint = new Point(inputOffX, startPoint.Y);
                    Point outputOffPoint = new Point(outputOffX, endPoint.Y);

                    this.line.Points.Clear();
                    this.line.Points.Add(startPoint);
                    this.line.Points.Add(inputOffPoint);
                    this.line.Points.Add(outputOffPoint);
                    this.line.Points.Add(endPoint);

                    this.line.Visibility = Visibility.Visible;
                }
            }
        }

        /// <summary>
        /// This is the event handler for Delete Button clik.
        /// </summary>
        /// <param name="sender">Sender of the Event.</param>
        /// <param name="e">Event arguments.</param>
        void deleteMenu_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                this.DeleteSelectedParmBindings();
            }
            catch (TridentCustomException exp)
            {
                TridentMessageBox.ShowTridentErrorMessageBox(exp.Message);
            }
        }

        /// <summary>
        /// Event handler for the left button click event.
        /// </summary>
        /// <param name="sender">Sender of the Event.</param>
        /// <param name="e">Event arguments.</param>
        void ParameterConnectionElement_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // Handled the Event.
            e.Handled = true;

            if (this.composerCtrl != null)
            {
                ParameterConnectionElement selected = sender as ParameterConnectionElement;

                if (!isCollapsedActivityLine)
                {
                    if (e.ChangedButton != MouseButton.Right)
                    {
                        // If Left button click.
                        if (!(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
                        {
                            // Update the style of the previous selected UI elements to "Unselected Style"
                            if (this.composerCtrl.SelectedParamConnections.Count > 0)
                            {
                                foreach (ParameterConnectionElement prevSelected in this.composerCtrl.SelectedParamConnections)
                                {
                                    // Apply the "Unselected Style".
                                    ApplyDeselectedStyle(prevSelected);
                                }
                            }

                            // Clear all the existing the ParameterConnectionElement 
                            this.composerCtrl.SelectedParamConnections.Clear();
                        }

                        // Check if the Selected binding is already available in the selected list
                        if (!this.composerCtrl.SelectedParamConnections.Contains(selected))
                        {
                            // Add the current ParameterConnectionElement to the Selected List.
                            this.composerCtrl.SelectedParamConnections.Add(selected);

                            // Update the style of UI element selected to "Selected Style".
                            ParameterConnectionElement.ApplySelectedStyle(selected);
                        }
                        else
                        {
                            // If already selected apply default style
                            ParameterConnectionElement.ApplyDeselectedStyle(selected);

                            // Remove the selected UI element from ParameterConnectionElement list
                            this.composerCtrl.SelectedParamConnections.Remove(selected);
                        }
                    }
                    else
                    {
                        // Check if the Selected binding is already available in the selected list
                        if (!this.composerCtrl.SelectedParamConnections.Contains(selected))
                        {
                            if ((Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
                            {
                                // if there are any previous selected element Enaable the context menu.
                                if (this.composerCtrl.SelectedParamConnections.Count > 0)
                                {
                                    foreach (MenuItem item in this.ContextMenu.Items)
                                    {
                                        item.IsEnabled = true;
                                    }
                                }
                            }
                            else
                            {
                                // Update the style of the previous selected UI elements to "Unselected Style"
                                if (this.composerCtrl.SelectedParamConnections.Count > 0)
                                {
                                    foreach (ParameterConnectionElement prevSelected in this.composerCtrl.SelectedParamConnections)
                                    {
                                        // Apply the "Unselected Style".
                                        ApplyDeselectedStyle(prevSelected);
                                    }
                                }

                                // Clear all the existing the ParameterConnectionElement 
                                this.composerCtrl.SelectedParamConnections.Clear();

                                // Add the current ParameterConnectionElement to the Selected List.
                                this.composerCtrl.SelectedParamConnections.Add(selected);

                                // Update the style of UI element selected to "Selected Style".
                                ParameterConnectionElement.ApplySelectedStyle(selected);
                            }
                        }
                    }
                }
                else
                {
                    TridentMessageBox.ShowTridentInformationMessageBox(
                        TridentResourceManager.GetString("CannotDeleteCollapsedLine"));
                }

                this.Focus();
            }
        }

        /// <summary>
        /// Event handler for the Deleted Key press.
        /// </summary>
        /// <param name="sender">Sender of the Event.</param>
        /// <param name="e">Event arguments.</param>
        void ParameterConnectionElement_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (Key.Delete == e.Key)
            {
                try
                {
                    this.DeleteSelectedParmBindings();
                }
                catch (TridentCustomException exp)
                {
                    TridentMessageBox.ShowTridentErrorMessageBox(exp.Message);
                }
            }
        }

        /// <summary>
        /// This function is used to Delete the databindings selected.
        /// </summary>
        private void DeleteSelectedParmBindings()
        {
            MessageBoxResult confirmRes;
            confirmRes = TridentMessageBox.ShowTridentConfirmationYesNoMessageBox(
                TridentResourceManager.GetString("ConfirmDeleteParamMesg"));

            if (MessageBoxResult.Yes == confirmRes)
            {
                if (this.composerCtrl.SelectedParamConnections.Count > 0)
                {
                    foreach (ParameterConnectionElement paramSelected in this.composerCtrl.SelectedParamConnections)
                    {
                        ParameterDescriptionModel delParamModel = 
                            paramSelected.DataContext as ParameterDescriptionModel;

                        if (null != delParamModel)
                        {
                            delParamModel.IsDatabound = false;
                            delParamModel.Value = null;
                        }
                    }

                    this.composerCtrl.SelectedParamConnections.Clear();

                    try
                    {
                        // Set the workflow has been updated. 
                        this.composerCtrl.IsWFCommitted = false;

                        // Reload the current wotkflow Model.
                        // true is passed as Parameter to specify only databinds has changed.
                        this.composerCtrl.RefreshUI();
                        
                    }
                    catch (Exception exp)
                    {
                        TridentErrorHandler.HandleAndThrowBusinessLogicException(
                            exp,
                            TridentErrorConstants.ErrorNumber90000012,
                            exp.Message);
                    }
                }
                else
                {
                    TridentMessageBox.ShowTridentWarningMessageBox(
                        TridentResourceManager.GetString("InvalidParamSelection"));
                }
            }
        }

        /// <summary>
        /// This function is used to create the conText Menu for the databindings.
        /// </summary>
        private void CreateContextMenu()
        {
            this.ContextMenu = new ContextMenu();

            MenuItem deleteMenu = new MenuItem();
            deleteMenu.Header = TridentResourceManager.GetString("DeleteParamLabel");
            deleteMenu.Icon = GetIcon(@"pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/deleteParameterBinding.png");
            deleteMenu.Click += new RoutedEventHandler(deleteMenu_Click);
            this.ContextMenu.Items.Add(deleteMenu);

            foreach (MenuItem item in this.ContextMenu.Items)
            {
                item.IsEnabled = false;
            }
        }

        /// <summary>
        /// Creates the Image object
        /// </summary>
        /// <param name="imagepath">string path of the image</param>
        /// <returns>Image</returns>
        private static Image GetIcon(string imagepath)
        {
            Image headerImage = new Image();
            ImageSourceConverter converter = new ImageSourceConverter();
            headerImage.Source = (ImageSource)converter.ConvertFromString(imagepath);
            return headerImage;
        }

        /// <summary>
        /// Get the composer control instance.
        /// </summary>
        private void GetComposerControl()
        {
            string composerCtrlName = (string)this.GetValue(ComposerControl.ComposerControlNameProperty);
            this.composerCtrl = Application.Current.MainWindow.FindName(composerCtrlName) as ComposerControl;
        }
    }
}
